using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml.Serialization;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;

using Tomahawk.Runtime.Physics.Descriptors;

namespace Tomahawk.Processors
{
    #region XML Collision Importer
    /// <summary>
    /// Creates a PhysicsActorDesc from an XML serialization (for debugging purposes)
    /// </summary>
    [ContentImporter(".xml", DefaultProcessor = "CollisionProcessor",
          DisplayName = "XML Collision Importer - Tomahawk Engine")]
    public class XMLCollisionImporter : ContentImporter<PhysicsActorDesc>
    {
        public override PhysicsActorDesc Import(string filename, ContentImporterContext context)
        {
            PhysicsActorDesc output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    XmlSerializer x = new XmlSerializer(typeof(PhysicsActorDesc));
                    output = (PhysicsActorDesc)x.Deserialize(fs);
                }
            }
            catch
            {
                throw new InvalidContentException("Unknown error while importing the XML content file");
            }

            return output;
        }
    }
    #endregion

    #region PHS Collision Importer
    /// <summary>
    /// Creates a PhysicsActorDesc from an PHS file. This files are created in the Scythe editor
    /// that can be found here http://www.physicseditor.com/
    /// </summary>
    [ContentImporter(".phs", DefaultProcessor = "CollisionProcessor",
          DisplayName = "PHS Collision Importer - Tomahawk Engine")]
    public class PHSCollisionImporter : ContentImporter<PhysicsActorDesc>
    {
        #region PHSTypes
        protected class PHSString
        {
            public string data;

            public PHSString(BinaryReader reader)
            {
                int size = reader.ReadInt32();
                byte[] bytes = reader.ReadBytes(size);
                data = Encoding.ASCII.GetString(bytes);
            }
            public PHSString(int size, BinaryReader reader)
            {
                byte[] bytes = reader.ReadBytes(size);
                data = Encoding.ASCII.GetString(bytes);
            }
        }

        protected class PHSVector
        {
            public Vector3 data;

            public PHSVector(BinaryReader reader)
            {
                data.X = reader.ReadSingle();
                data.Y = reader.ReadSingle();
                data.Z = reader.ReadSingle();
            }
        }

        protected class PHSMatrix
        {
            public Matrix data;

            public PHSMatrix(BinaryReader reader)
            {
                data.M11 = reader.ReadSingle();
                data.M12 = reader.ReadSingle();
                data.M13 = reader.ReadSingle();
                data.M14 = 0.0f;

                data.M21 = reader.ReadSingle();
                data.M22 = reader.ReadSingle();
                data.M23 = reader.ReadSingle();
                data.M24 = 0.0f;

                data.M31 = reader.ReadSingle();
                data.M32 = reader.ReadSingle();
                data.M33 = reader.ReadSingle();
                data.M34 = 0.0f;

                data.M41 = 0.0f;
                data.M42 = 0.0f;
                data.M43 = 0.0f;
                data.M44 = 1.0f;
            }
        }

        protected class PHSIntBuffer
        {
            int[] data;

            public PHSIntBuffer(int size, BinaryReader reader)
            {
                data = new int[size];
                for (int idx = 0; idx < size; ++idx)
                {
                    data[idx] = reader.ReadInt16();
                }
            }
        }

        protected class PHSFloatBuffer
        {
            public float[] data;

            public PHSFloatBuffer(int size, BinaryReader reader)
            {
                data = new float[size];
                for (int idx = 0; idx < size; ++idx)
                {
                    data[idx] = reader.ReadSingle();
                }
            }
        }
        #endregion

        #region PHSHeader
        protected class PHSHeader
        {
            public float version;
            public int numActors;

            public PHSHeader(BinaryReader reader)
            {
                version = reader.ReadSingle();
                numActors = reader.ReadInt32();
            }
        }
        #endregion

        #region PHSMesh
        protected class PHSMesh
        {
            public bool isConvex;
            public int numVerts;
            public int numTris;
            public PHSFloatBuffer verts;
            public PHSFloatBuffer normals;
            public PHSIntBuffer tris;
            public bool doubleSided;
            public bool smooth;

            public PHSMesh(BinaryReader reader)
            {
                isConvex = reader.ReadBoolean();
                numVerts = reader.ReadInt32();
                numTris = reader.ReadInt32();
                verts = new PHSFloatBuffer(numVerts * 3, reader);
                normals = new PHSFloatBuffer(numVerts * 3, reader);
                tris = new PHSIntBuffer(numTris * 3, reader);
                doubleSided = reader.ReadBoolean();
                smooth = reader.ReadBoolean();
            }
        }
        #endregion

        #region PHSPrimitive
        protected class PHSPrimitive
        {
            public enum Type
            {
                Box = 3,
                Sphere = 4,
                Capsule = 5,
                Cylinder = 6,
                Cone = 7,
                Elipse = 8,
                Mesh = 9,
                None = 10
            }

            public PHSMatrix rotation;
            public PHSVector position;
            public Type type;
            public PHSFloatBuffer custom;
            public int group;
            public PHSVector size;
            public int materialIndex;
            public float density;
            public float mass;
            public float skinWidth;
            public PHSString meshFile;
            public PHSString cookedFile;
            public bool useMeshFileOnly;
            public bool useMeshData;
            public PHSMesh mesh;
			public bool fluidDrain;
			public bool triggerOnEnter;
			public bool triggerOnStay;
            public bool triggerOnLeave;


            public PHSPrimitive(BinaryReader reader)
            {
                rotation = new PHSMatrix(reader);
                position = new PHSVector(reader);
                type = (Type) reader.ReadInt32();
                custom = new PHSFloatBuffer(8, reader);
                group = reader.ReadInt32();
                size = new PHSVector(reader);
                materialIndex = reader.ReadInt32();
                density = reader.ReadSingle();
                mass = reader.ReadSingle();
                skinWidth = reader.ReadSingle();
                meshFile = new PHSString(reader);
                cookedFile = new PHSString(reader);
                useMeshFileOnly = reader.ReadBoolean();
                useMeshData = reader.ReadBoolean();
                mesh = (type == Type.Mesh) ? new PHSMesh(reader) : null;
                fluidDrain = reader.ReadBoolean();
                triggerOnEnter = reader.ReadBoolean();
                triggerOnStay = reader.ReadBoolean();
                triggerOnLeave = reader.ReadBoolean();
            }
        }
        #endregion

        #region PHSModel
        protected class PHSModel
        {
            public PHSMatrix rotation;
            public PHSVector position;
            public int type;
            public PHSFloatBuffer custom;
            public PHSString modelName;
            public PHSVector scale;

            public PHSModel(BinaryReader reader)
            {
                rotation = new PHSMatrix(reader);
                position = new PHSVector(reader);
                type = reader.ReadInt32();
                custom = new PHSFloatBuffer(8, reader);
                modelName = new PHSString(reader);
                scale = new PHSVector(reader);
            }
        }
        #endregion

        #region PHSLight
        protected class PHSLight
        {
			public PHSVector rgb;
            public PHSVector specular;
            public float range;
            public float innerCone;
            public float outerCone;
            public float falloff;

            public PHSLight(BinaryReader reader)
            {
                rgb = new PHSVector(reader);
                specular = new PHSVector(reader);
                range = reader.ReadSingle();
                innerCone = reader.ReadSingle();
                outerCone = reader.ReadSingle();
                falloff = reader.ReadSingle();
            }
        }
        #endregion

        #region PHSSkeletalModel
        protected class PHSSkeletalModel
        {
            public PHSMatrix rotation;
            public PHSVector position;
            public int type;
            public PHSFloatBuffer custom;
            public PHSString modelName;
            public PHSVector scale;

            public PHSSkeletalModel(BinaryReader reader)
            {
                rotation = new PHSMatrix(reader);
                position = new PHSVector(reader);
                type = reader.ReadInt32();
                custom = new PHSFloatBuffer(8, reader);
                modelName = new PHSString(reader);
                scale = new PHSVector(reader);
            }
        }
        #endregion

        #region PHSMotor
        protected class PHSMotor
        {
            public bool active;
            public float targetVelocity;
            public float maxForce;
            public bool freeSpinning;

            public PHSMotor(BinaryReader reader)
            {
                active = reader.ReadBoolean();
                targetVelocity = reader.ReadSingle();
                maxForce = reader.ReadSingle();
                freeSpinning = reader.ReadBoolean();
            }
        }
        #endregion

        #region PHSSpring
        protected class PHSSpring
        {
            public bool active;
	        public float value;
	        public float spring;
	        public float damper;

            public PHSSpring(BinaryReader reader)
            {
                active = reader.ReadBoolean();
                value = reader.ReadSingle();
                spring = reader.ReadSingle();
                damper = reader.ReadSingle();
            }
        }
        #endregion

        #region PHSJoint
        protected class PHSJoint
        {
            public PHSMatrix rotation;
            public PHSVector position;
            public int type;
            public PHSFloatBuffer custom;
            public int function;
            public PHSFloatBuffer limits;
            public bool limitsOn;
            public int actor0;
            public int actor1;
            public float breakable0;
            public float breakable1;
            public bool selfCollision;
            public int maxIterations;
            public bool pointForActor1;
            public PHSVector pointPosition;
            public PHSVector planeNormal0;
            public PHSVector planeNormal1;
            public PHSVector planeNormal2;
            public PHSVector planePosition0;
            public PHSVector planePosition1;
            public PHSVector planePosition2;
            public int projectionMode;
            public float projectionDistance;
            public float projectionAngle;
            public float stiffness;
            public PHSFloatBuffer empty;
            public PHSMotor motor;
            public PHSSpring twistSpring;
            public PHSSpring swingSpring;
            public PHSSpring distanceSpring;
            public PHSString name;

            private int ReadActorId(BinaryReader reader)
            {
                byte[] intBytes = reader.ReadBytes(4);
                Array.Reverse(intBytes);
                return BitConverter.ToInt32(intBytes, 0);
            }

            public PHSJoint(BinaryReader reader)
            {
                position = new PHSVector(reader);
                rotation = new PHSMatrix(reader);
                type = reader.ReadInt32();
                custom = new PHSFloatBuffer(8, reader);

                function = reader.ReadInt32();
                limits = new PHSFloatBuffer(4, reader);
                limitsOn = reader.ReadBoolean();
                actor0 = ReadActorId(reader);
                actor1 = ReadActorId(reader);
                breakable0 = reader.ReadSingle();
                breakable1 = reader.ReadSingle();
                selfCollision = reader.ReadBoolean();
                maxIterations = reader.ReadInt32();
                pointForActor1 = reader.ReadBoolean();
                pointPosition = new PHSVector(reader);
                planeNormal0 = new PHSVector(reader);
                planeNormal1 = new PHSVector(reader);
                planeNormal2 = new PHSVector(reader);
                planePosition0 = new PHSVector(reader);
                planePosition1 = new PHSVector(reader);
                planePosition2 = new PHSVector(reader);
                projectionMode = reader.ReadInt32();
                projectionDistance = reader.ReadSingle();
                projectionAngle = reader.ReadSingle();
                stiffness = reader.ReadSingle();
                empty = new PHSFloatBuffer(3, reader);
                motor = new PHSMotor(reader);
                twistSpring = new PHSSpring(reader);
                swingSpring = new PHSSpring(reader);
                distanceSpring = new PHSSpring(reader);
                name = new PHSString(300, reader);
            }
        }
        #endregion

        #region PHSActor
        protected class PHSActor
        {
		    public int numPrimitives;
            public int numModels;
            public int numLights;

            public int function;
            public PHSVector position;
            public int type;
            public int group;
            public bool staticEntity;
            public float density;
            public float angularDamping;
            public float linearDamping;
            public PHSFloatBuffer custom;
            public float mass;
            public PHSFloatBuffer sleepVelocity;
            public PHSVector centerOfMass;
            public PHSMatrix centerOfMassOrientation;
            public bool manualCOM;
            public PHSString name;
            public bool disableFluidCollision;
            public bool disableGravity;
            public int maxIterations;

            public int boneModelID;
            public PHSString boneName;
            public PHSVector bonePosition;
            public PHSMatrix boneRotation;
            public bool isRootBone;
            public List<PHSPrimitive> primitives;
            public List<PHSModel> models;
            public List<PHSLight> lights;

            public PHSActor(BinaryReader reader)
            {
                numPrimitives = reader.ReadInt32();
                numModels = reader.ReadInt32();
                numLights = reader.ReadInt32();

                function = reader.ReadInt32();
                position = new PHSVector(reader);
                type = reader.ReadInt32();
                group = reader.ReadInt32();
                staticEntity = reader.ReadBoolean();
                density = reader.ReadSingle();
                angularDamping = reader.ReadSingle();
                linearDamping = reader.ReadSingle();
                custom = new PHSFloatBuffer(8, reader);
                mass = reader.ReadSingle();
                sleepVelocity = new PHSFloatBuffer(2, reader);
                centerOfMass = new PHSVector(reader);
                centerOfMassOrientation = new PHSMatrix(reader);
                manualCOM = reader.ReadBoolean();
                name = new PHSString(reader);
                disableFluidCollision = reader.ReadBoolean();
                disableGravity = reader.ReadBoolean();
                maxIterations = reader.ReadInt32();

                boneModelID = reader.ReadInt32();
                boneName = new PHSString(reader);
                bonePosition = new PHSVector(reader);
                boneRotation = new PHSMatrix(reader);
                isRootBone = reader.ReadBoolean();

                primitives = new List<PHSPrimitive>();
                for (int primitiveIdx = 0; primitiveIdx < numPrimitives; ++primitiveIdx)
                {
                    primitives.Add(new PHSPrimitive(reader));
                }

                models = new List<PHSModel>();
                for (int modelIdx = 0; modelIdx < numModels; ++modelIdx)
                {
                    models.Add(new PHSModel(reader));
                }

                lights = new List<PHSLight>();
                for (int lightIdx = 0; lightIdx < numLights; ++lightIdx)
                {
                    lights.Add(new PHSLight(reader));
                }
            }
        }
        #endregion

        #region PHSFile
        protected class PHSFile
        {
            public PHSHeader header;
            public List<PHSActor> actors;

            public int numSkeletalModels;
            public List<PHSSkeletalModel> skeletalModels;

            public int numJoints;
            public List<PHSJoint> joints;

            public PHSFile(BinaryReader reader)
            {
                header = new PHSHeader(reader);

                if (header.version < 1.4f)
                {
                    throw new InvalidContentException("Old PHS file format");
                }

                actors = new List<PHSActor>();
                for (int actorIdx = 0; actorIdx < header.numActors; ++actorIdx)
                {
                    actors.Add(new PHSActor(reader));
                }

                numSkeletalModels = reader.ReadInt32();
                skeletalModels = new List<PHSSkeletalModel>();
                for (int skeletalModelIdx = 0; skeletalModelIdx < numSkeletalModels; ++skeletalModelIdx)
                {
                    skeletalModels.Add(new PHSSkeletalModel(reader));
                }

                numJoints = reader.ReadInt32();
                joints = new List<PHSJoint>();
                for (int jointIdx = 0; jointIdx < numJoints; ++jointIdx)
                {
                    joints.Add(new PHSJoint(reader));
                }
            }
        }
        #endregion

        public override PhysicsActorDesc Import(string filename, ContentImporterContext context)
        {
            PhysicsActorDesc output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    BinaryReader reader = new BinaryReader(fs);
                    PHSFile file = new PHSFile(reader);

                    output = new PhysicsActorDesc();

                    foreach (PHSActor actor in file.actors)
                    {
                        PhysicsBodyDesc body = new PhysicsBodyDesc();

                        body.mass = actor.mass;
                        foreach (PHSPrimitive primitive in actor.primitives)
                        {
                            PhysicsShapeDesc shape = null;
                            switch (primitive.type)
                            {
                                case PHSPrimitive.Type.Box:
                                    {
                                        PhysicsBoxShapeDesc box = new PhysicsBoxShapeDesc();
                                        box.Extents = primitive.size.data;
                                        shape = box;

                                    } break;
                                case PHSPrimitive.Type.Capsule:
                                    {
                                        PhysicsCapsuleShapeDesc capsule = new PhysicsCapsuleShapeDesc();
                                        capsule.radius = primitive.size.data.X;
                                        capsule.height = primitive.size.data.Y;
                                        shape = capsule;
                                    } break;
                                case PHSPrimitive.Type.Sphere:
                                    {
                                        PhysicsSphereShapeDesc sphere = new PhysicsSphereShapeDesc();
                                        sphere.radius = primitive.size.data.X;
                                        shape = sphere;
                                    } break;
                                case PHSPrimitive.Type.Mesh:
                                    {
                                        PhysicsMeshShapeDesc mesh = new PhysicsMeshShapeDesc();
                                        //@TODO [CLOPEZ]: Read mesh vertices and indices
                                        shape = mesh;
                                    } break;
                            }
                            if (shape != null)
                            {
                                shape.localRotation = Quaternion.CreateFromRotationMatrix(primitive.rotation.data);
                                shape.localTransform = primitive.position.data;
                                shape.materialID = (MaterialID)primitive.materialIndex;
                                shape.elasticity = primitive.custom.data[0];
                                shape.staticRoughness = primitive.custom.data[1];
                                shape.dynamicRoughness = primitive.custom.data[2];

                                body.shapes.Add(shape);
                            }
                        }

                        output.bodies.Add(actor.name.data, body);
                    }
                }
            }
            catch
            {
                throw new InvalidContentException("Unknown error while importing the PHS content file");
            }

            return output;
        }
    }
    #endregion
}